package br.com.vexillum.control.validator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

import org.apache.commons.lang3.time.DateUtils;
import org.springframework.util.StringUtils;

import br.com.vexillum.control.manager.ExceptionManager;
import br.com.vexillum.model.CommonEntity;
import br.com.vexillum.model.annotations.Validate;
import br.com.vexillum.util.Message;
import br.com.vexillum.util.ReflectionUtils;
import br.com.vexillum.util.Return;

public class Validator {

	protected Map<String, Object> mapData;
	protected CommonEntity entity;
	protected String action;
	
	@SuppressWarnings("rawtypes")
	private Class thisClass;

	public Validator(Map<String, Object> mapData) {
		this.mapData = mapData;
		this.entity = (CommonEntity) mapData.get("entity");
		this.action = (String) mapData.get("action");
		this.thisClass = this.getClass();
	}

	@SuppressWarnings("unchecked")
	public Return validate() {
		Return ret = new Return(true);
		try {
			String methodName = "validate" + StringUtils.capitalize(action);
			if(ReflectionUtils.haveMethod(methodName, thisClass)){
				Method m = thisClass.getMethod(methodName, new Class[]{ });
				ret.concat((Return) m.invoke(this, new Object[] { }));
			}
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		}
 		return ret;
	}
	
	protected Return validateModel(){
		Return ret = new Return(true);
		try {
			Field[] campos = ReflectionUtils.getFields(entity.getClass());
			for (Field f : campos) {
				ret.concat(validateAttribute(f));
			}
			return ret;
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		} 
		return ret;
	}
	
	protected Return validateAttribute(String name){
		Field[] campos = ReflectionUtils.getFields(entity.getClass());
		Return ret = new Return(true);
		try {
			Boolean flag = false;
			for (Field f : campos) {
				if(f.getName().equalsIgnoreCase(name)){
					ret.concat(validateAttribute(f));
					flag = true;
					break;
				}
			}
			if(flag) throw new NoSuchFieldException();
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		}
		return ret;
	}
	
	protected Return validateAttribute(String fieldName, String validation){
		Field[] campos = ReflectionUtils.getFields(entity.getClass());
		Return ret = new Return(true);
		try {
			Boolean flag = false;
			for (Field f : campos) {
				if(f.getName().equalsIgnoreCase(fieldName)){
					ret.concat(validateAttribute(f, validation));
					flag = true;
					break;
				}
			}
			if(flag) throw new NoSuchFieldException();
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		}
		return ret;
	}
	
	@SuppressWarnings("unchecked")
	protected Return validateAttribute(Field field){
		Return ret = new Return(true);
		try {
			field.setAccessible(true);
			if (field.isAnnotationPresent(Validate.class)) {
				Validate annot = field.getAnnotation(Validate.class);
				for (Object method : annot.getClass().getDeclaredMethods()) {
					if(ReflectionUtils.haveMethod(((Method)method).getName(), thisClass)){
						Method m = thisClass.getMethod(((Method)method).getName(), new Class[]{ String.class, Object.class, Object.class});
						Object obj = ReflectionUtils.getMethodValue(annot, ((Method)method).getName());
						ret.concat((Return) m.invoke(this, new Object[] { field.getName(), field.get(entity), obj}));
					}
				}
			}
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		}
		return ret;
	}
	
	@SuppressWarnings("unchecked")
	protected Return validateAttribute(Field field, String validation){
		Return ret = new Return(true);
		try {
			field.setAccessible(true);
			if (field.isAnnotationPresent(Validate.class)) {
				Validate annot = field.getAnnotation(Validate.class);
				for (Object method : annot.getClass().getDeclaredMethods()) {
					if(((String)method).equalsIgnoreCase(validation)){
						if(ReflectionUtils.haveMethod(((Method)method).getName(), thisClass)){
							Method m = thisClass.getMethod(((Method)method).getName(), new Class[]{ String.class, Object.class, Object.class});
							Object obj = ReflectionUtils.getMethodValue(annot, ((Method)method).getName());
							ret.concat((Return) m.invoke(this, new Object[] { field.getName(), field.get(entity), obj}));
						}
					}
				}
			}
		} catch (Exception e) {
			ret = new ExceptionManager(e).treatException();
		}
		return ret;
	}
	
	//Métodos de validação das ações genéricas
	public Return validateSave(){
		return validateModel();
	}
	
	public Return validateUpdate(){
		return validateModel();
	}

	//INICIO METODOS DAS VALIDA��ES
	public Return notNull(String name, Object valField, Object valAnoted){
		Return ret = new Return(true);		
		if((Boolean) valAnoted){
			if(valField == null || ((String) valField).isEmpty())
				ret = creatReturn(name, "Campo não pode ser nulo!");
		} 
		return ret;
	}
	
	public Return min(String name, Object valField, Object valAnoted){
		Return ret = new Return(true);
		
		if(isNotNulle(valField) && isNotNulle(valAnoted)){
			Integer v1 = valField.toString().length();
			if((v1 < (Integer)valAnoted))
				ret = creatReturn(name, "Quantidade mínima "+(Integer)valAnoted);
		}
		return ret;
	}
	
	public Return max(String name, Object valField, Object valAnoted){
		Return ret = new Return(true);
		if(isNotNulle(valField) && isNotNulle(valAnoted)){
			Integer v1 = valField.toString().length();
		if(v1 > (Integer)valAnoted)
			ret = creatReturn(name, "Quantidade máxima "+(Integer)valAnoted);
		}
		return ret;
	}
	
	public Return past(String name, Object valField, Object valAnoted){
		Return ret = new Return(true);		
		if((Boolean) valAnoted){
			if(DateUtils.round(valField, 0).before(new Date()))
				ret = creatReturn(name, "Data não pode estar no passado");
		} 
		return ret;
	}
	
	public Return future(String name, Object valField, Object valAnoted){
		Return ret = new Return(true);		
		if((Boolean) valAnoted){
			if(DateUtils.round(valField, 0).after(new Date()))
				ret = creatReturn(name, "Data não pode estar no futuro");
		} 
		return ret;
	}
	
	//FIM METODOS DAS VALIDA��ES
	//TODO Arrumar uma maneira de puxar essas mensagens do arquivo de properties espec�fico de cada sistema
	
	public Boolean isNotNulle(Object obj){
		if(obj == null || obj == "")
			return false;
		return true;
	}
	
	public Return creatReturn(String name, String msg){
		return new Return(false, new Message(name, msg));
	}
}
